package shape;

import javax.media.opengl.GL2;

import panel.control.HasProperties;
import shape.material.Material;

import window.Axis;
import window.Hue;

public abstract class Shape implements HasProperties {
	
	public static final int SPHERE 		= 0;
	public static final int CUBE 		= 1;
	public static final int RECTANGLE	= 2;
	public static final int CONE		= 3;
	public static final int TORUS		= 4;
	public static final int TEAPOT		= 5;
	public static String[]	SHAPE_NAME;
	
	public static final int	PROPERTY_POSITION	= 0;
	public static final int PROPERTY_SIZE		= 1;
	public static final int PROPERTY_ROTATION	= 2;
	public static final int PROPERTY_COLOR		= 3;
	public static final int[] PROPERTY_ALL		= {PROPERTY_POSITION, PROPERTY_ROTATION, PROPERTY_SIZE, PROPERTY_COLOR};
	
	protected double position[];
	protected double size[];
	protected double rotation[];
	protected double color[];
	protected Material material;
	
	protected boolean selected;
	
	public Shape( double x, double y, double z ) {
		position = new double[3];
		size = new double[3];
		rotation = new double[3];
		color = new double[4];
		selected = false;
		
		position[Axis.X] = x;
		position[Axis.Y] = y;
		position[Axis.Z] = z;
		
		material = new Material();
		
		for (int i: Axis.ALL) {
			size[i] = 1.0;
			rotation[i] = 0.0;
		}

		for (int c: Hue.ALL) {
			color[c] = 1.0;
		}
		color[Hue.A] = 1.0;
		
	}
	
	public Shape() {
		this(0.0, 0.0, 0.0);
	} 
	
	/* ----------------------------------------------------
	 * Static functions
	 */
	
	public static Shape create( int shape ) {
		switch (shape) {
			case SPHERE: return new Sphere();
			case CUBE: return new Cube();
			case RECTANGLE: return new Rectangle();
			case CONE: return new Cone();
			case TORUS: return new Torus();
			case TEAPOT: return new Teapot();
		}
		
		return null;
	}
	
	public static void init() {
		SHAPE_NAME = new String[6];
		SHAPE_NAME[ SPHERE		] = "Sphere";
		SHAPE_NAME[ CUBE		] = "Cube";
		SHAPE_NAME[ RECTANGLE	] = "Rectangle";
		SHAPE_NAME[ CONE		] = "Cone";
		SHAPE_NAME[ TORUS		] = "Torus";
		SHAPE_NAME[ TEAPOT		] = "Teapot";
	}
	
	/* ----------------------------------------------------
	 * Displaying functions
	 */
	
	public void display ( GL2 gl, boolean considerSelection ) {
		gl.glPushMatrix();
		gl.glTranslated( position[Axis.X], position[Axis.Y], position[Axis.Z] );
		gl.glRotated( rotation[Axis.X], 1, 0, 0 );
		gl.glRotated( rotation[Axis.Y], 0, 1, 0 );
		gl.glRotated( rotation[Axis.Z], 0, 0, 1 );
		gl.glScaled( size[Axis.X], size[Axis.Y], size[Axis.Z] );
		gl.glColor3d( color[Hue.R], color[Hue.G], color[Hue.B] );
		material.apply(gl);
		
		if (considerSelection && selected)
			drawSelected(gl);
		else
			draw(gl);
		
		gl.glPopMatrix();
	}
	
	protected abstract void draw( GL2 gl );
	
	protected void drawSelected( GL2 gl ) {
		draw(gl);
	}
	
	/* ----------------------------------------------------
	 * Getters and setters 
	 */
	
	public boolean setSelected( boolean selected ) {
		return this.selected != (this.selected = selected);
	}
	
	public boolean isSelected() {
		return selected;
	}
	
	public boolean setPosition( double p, int axis ) {
		return position[axis] != (position[axis] = p);
	}
	
	public boolean setPosition( double x, double y, double z ) {
		boolean hasChanged = setPosition(x, Axis.X);
		hasChanged = setPosition(y, Axis.Y) || hasChanged;
		hasChanged = setPosition(z, Axis.Z) || hasChanged;
		
		return hasChanged;
	}
	
	public boolean move( double d, int axis ) {
		return setPosition( position[axis] + d, axis );
	}
	
	public boolean move( double dx, double dy, double dz ) {
		return setPosition( position[Axis.X] + dx,
							position[Axis.Y] + dy,
							position[Axis.Z] + dz );
	}
	
	public boolean setSize( double p, int axis ) {
		return size[axis] != (size[axis] = p);
	}
	
	public boolean setSize( double x, double y, double z ) {
		boolean hasChanged = setSize(x, Axis.X);
		hasChanged = setSize(y, Axis.Y) || hasChanged;
		hasChanged = setSize(z, Axis.Z) || hasChanged;
		
		return hasChanged;
	}
	
	public boolean scale( double d, int axis ) {
		return setSize( size[axis] + d, axis );
	}
	
	public boolean scale( double dx, double dy, double dz ) {
		return setSize( size[Axis.X] + dx,
						size[Axis.Y] + dy, 
						size[Axis.Z] + dz );
	}
	
	public boolean setRotation( double a, int axis ) {
		System.out.println(rotation[axis]);
		return rotation[axis] != (rotation[axis] = a % 360);
	}
	
	public boolean setRotation( double x, double y, double z ) {
		boolean hasChanged = setRotation(x, Axis.X);
		hasChanged = setRotation(y, Axis.Y) || hasChanged;
		hasChanged = setRotation(z, Axis.Z) || hasChanged;
		
		return hasChanged;
	}
	
	public boolean rotate( double a, int axis ) {
		return setRotation(rotation[axis] + a, axis );
	}
	
	public boolean rotate( double ax, double ay, double az ) {
		return setRotation( rotation[Axis.X] + ax, 
							rotation[Axis.Y] + ay, 
							rotation[Axis.Z] + az );
	}
	
	public boolean setColor( double c, int hue ) {
		return color[hue] != (color[hue] = Math.max(Math.min(c, 1.0), 0.0));
	}
	
	public boolean setColor( double r, double g, double b ) {
		boolean hasChanged = setColor(r, Hue.R);
		hasChanged = setColor(g, Hue.G) || hasChanged;
		hasChanged = setColor(b, Hue.B) || hasChanged;
		
		return hasChanged;
	}
	
	public boolean setColor( double r, double g, double b, double a ) {
		boolean hasChanged = setColor(r, Hue.R);
		hasChanged = setColor(g, Hue.G) || hasChanged;
		hasChanged = setColor(b, Hue.B) || hasChanged;
		hasChanged = setColor(a, Hue.A) || hasChanged;
		
		return hasChanged;
	}
	
	public boolean recolor( double d, int hue ) {
		return setColor( color[hue] + d, hue );
	}
	
	public boolean recolor( double dr, double dg, double db ) {
		return setColor( color[Hue.R] + dr,
						color[Hue.G] + dg,
						color[Hue.B] + db );
	}
	
	public boolean recolor( double dr, double dg, double db, double da ) {
		return setColor( color[Hue.R] + dr,
						color[Hue.G] + dg,
						color[Hue.B] + db,
						color[Hue.A] + da );
	}
	
	/* ----------------------------------------------------
	 * General property functions
	 * (from HasProperties interface)
	 */
	
	public double getProperty( int index, int property ) {
		switch (property) {
			case PROPERTY_POSITION:	return position[index];
			case PROPERTY_ROTATION:	return rotation[index];
			case PROPERTY_SIZE:		return size[index];
			case PROPERTY_COLOR:	return color[index];
		}
		
		return 0.0;
	}
	
	public boolean setProperty( double value, int index, int property ) {
		switch (property) {
			case PROPERTY_POSITION:	return setPosition(value, index);
			case PROPERTY_ROTATION:	return setRotation(value, index);
			case PROPERTY_SIZE:		return setSize(value, index);
			case PROPERTY_COLOR:	return setColor(value, index);
		}
		
		return false;
	}
	
	public boolean changeProperty( double value, int index, int property ) {
		return setProperty( getProperty( index, property ) + value, index, property );
	}
}
